package top.guoziyang.mydb.backend.tbm;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.common.primitives.Bytes;

import top.guoziyang.mydb.backend.parser.statement.Create;
import top.guoziyang.mydb.backend.parser.statement.Delete;
import top.guoziyang.mydb.backend.parser.statement.Insert;
import top.guoziyang.mydb.backend.parser.statement.Select;
import top.guoziyang.mydb.backend.parser.statement.Update;
import top.guoziyang.mydb.backend.parser.statement.Where;
import top.guoziyang.mydb.backend.tbm.Field.ParseValueRes;
import top.guoziyang.mydb.backend.tm.TransactionManagerImpl;
import top.guoziyang.mydb.backend.utils.Panic;
import top.guoziyang.mydb.backend.utils.ParseStringRes;
import top.guoziyang.mydb.backend.utils.Parser;
import top.guoziyang.mydb.common.Error;

/**
 * Table 维护了表结构
 * 二进制结构如下：
 * [TableName][NextTable]
 * [Field1Uid][Field2Uid]...[FieldNUid]
 */
public class Table {
    TableManager tbm;
    long uid;
    String name;
    byte status;
    long nextUid;
    List<Field> fields = new ArrayList<>();

    /**
     *从存储中加载指定 UID 的表数据，并解析其元数据
     */
    public static Table loadTable(TableManager tbm, long uid) {
        byte[] raw = null;
        try {
            raw = ((TableManagerImpl)tbm).vm.read(TransactionManagerImpl.SUPER_XID, uid);
        } catch (Exception e) {
            Panic.panic(e);
        }
        assert raw != null;
        Table tb = new Table(tbm, uid);
        return tb.parseSelf(raw);
    }


    /**
     * 创建一个新的表对象并将其持久化到存储中
     */
    public static Table createTable(TableManager tbm, long nextUid, long xid, Create create) throws Exception {
        Table tb = new Table(tbm, create.tableName, nextUid);
        //遍历 Create 对象中的字段名和字段类型列表
        for(int i = 0; i < create.fieldName.length; i ++) {
            //为每个字段初始化字段名、类型以及是否需要索引。
            String fieldName = create.fieldName[i];
            String fieldType = create.fieldType[i];
            boolean indexed = false;
            //  判断当前字段是否在 Create.index 列表中，如果是，则标记为索引字段
            for(int j = 0; j < create.index.length; j ++) {
                if(fieldName.equals(create.index[j])) {
                    indexed = true;
                    break;
                }
            }
            // 创建新的字段对象并添加到表的字段列表中
            tb.fields.add(Field.createField(tb, xid, fieldName, fieldType, indexed));
        }
        return tb.persistSelf(xid);
    }

    public Table(TableManager tbm, long uid) {
        this.tbm = tbm;
        this.uid = uid;
    }

    public Table(TableManager tbm, String tableName, long nextUid) {
        this.tbm = tbm;
        this.name = tableName;
        this.nextUid = nextUid;
    }

    /**
     * 表对象的序列化:将二进制字节数据（raw）解析为表对象，包括表名、下一个 UID 和字段列表
     */
    private Table parseSelf(byte[] raw) {
        int position = 0;
        ParseStringRes res = Parser.parseString(raw);
        name = res.str;
        position += res.next;
        nextUid = Parser.parseLong(Arrays.copyOfRange(raw, position, position+8));
        position += 8;

        while(position < raw.length) {
            long uid = Parser.parseLong(Arrays.copyOfRange(raw, position, position+8));
            position += 8;
            fields.add(Field.loadField(this, uid));
        }
        return this;
    }

    /**
     * 表对象的反序列化:将表对象序列化为字节数组，并存储到持久化存储中。
     */
    private Table persistSelf(long xid) throws Exception {
        byte[] nameRaw = Parser.string2Byte(name);
        byte[] nextRaw = Parser.long2Byte(nextUid);
        byte[] fieldRaw = new byte[0];
        //遍历字段列表，将每个字段的 UID 转换为 8 字节长整型字节数组,使用 Bytes.concat 方法将所有字段 UID 字节数组拼接在一起。
        for(Field field : fields) {
            fieldRaw = Bytes.concat(fieldRaw, Parser.long2Byte(field.uid));
        }
        //将表名字节数组、nextUid 字节数组和字段 UID 字节数组组合成完整的表元数据,调用 vm.insert 方法，将数据写入存储，并为表分配一个UID.
        uid = ((TableManagerImpl)tbm).vm.insert(xid, Bytes.concat(nameRaw, nextRaw, fieldRaw));
        return this;
    }

    /**
     * 根据 Delete 命令的条件删除符合条件的记录
     */
    public int delete(long xid, Delete delete) throws Exception {
        List<Long> uids = parseWhere(delete.where);
        int count = 0;
        for (Long uid : uids) {
            if(((TableManagerImpl)tbm).vm.delete(xid, uid)) {
                count ++;
            }
        }
        return count; //返回删除的记录总数
    }

    /**
     * 根据 Update 命令更新符合条件的记录
     */
    public int update(long xid, Update update) throws Exception {
        List<Long> uids = parseWhere(update.where);
        Field fd = null;
        for (Field f : fields) { //查找需要更新的字段
            if(f.fieldName.equals(update.fieldName)) {
                fd = f;
                break;
            }
        }
        if(fd == null) {
            throw Error.FieldNotFoundException;
        }
        Object value = fd.string2Value(update.value); //将更新值转换为目标类型
        int count = 0;
        for (Long uid : uids) {
            byte[] raw = ((TableManagerImpl)tbm).vm.read(xid, uid); //读取原始数据
            if(raw == null) continue;

            ((TableManagerImpl)tbm).vm.delete(xid, uid); //删除旧记录
            // 解析记录并更新字段值
            Map<String, Object> entry = parseEntry(raw);
            entry.put(fd.fieldName, value);
            //重新序列化并插入新记录
            raw = entry2Raw(entry);
            long uuid = ((TableManagerImpl)tbm).vm.insert(xid, raw);
            
            count ++;
            //更新索引
            for (Field field : fields) {
                if(field.isIndexed()) {
                    field.insert(entry.get(field.fieldName), uuid);
                }
            }
        }
        return count;
    }


    /**
     * 根据查询条件读取符合条件的记录，并格式化为字符串返回
     * 假设假设表字段为：id (int32) name (string) age (int32)
     * 存储中有以下记录： UID 1 -> [00 00 00 01] [04 'A' 'l' 'i' 'c' 'e'] [00 00 00 19]
     *                 UID 2 -> [00 00 00 02] [03 'B' 'o' 'b'] [00 00 00 21]
     *  查询：WHERE age > 20
     *
     */
    public String read(long xid, Select read) throws Exception {
        List<Long> uids = parseWhere(read.where);  // uids=[2]
        StringBuilder sb = new StringBuilder();
        for (Long uid : uids) {
            byte[] raw = ((TableManagerImpl)tbm).vm.read(xid, uid); //[00 00 00 02] [03 'B' 'o' 'b'] [00 00 00 21]
            if(raw == null) continue;
            Map<String, Object> entry = parseEntry(raw); //调用 parseEntry 将字节数据解析为键值对记录 { "id": 2, "name": "Bob", "age": 21 }
            sb.append(printEntry(entry)).append("\n"); //调用 printEntry 格式化记录为字符串并追加到结果中 [2, Bob, 21]
        }
        return sb.toString();
    }

    /**
     * 插入新的记录，支持带索引字段的动态更新.
     * 假设假设表字段为：id (int32) name (string) age (int32),id 字段为索引字段
     *      INSERT INTO table VALUES (3, "Charlie", 30)
     */
    public void insert(long xid, Insert insert) throws Exception {
        Map<String, Object> entry = string2Entry(insert.values); //转换输入值为键值对记录,{ "id": 3, "name": "Charlie", "age": 30 }
        byte[] raw = entry2Raw(entry); //序列化记录为字节数组,[00 00 00 03] [07 'C' 'h' 'a' 'r' 'l' 'i' 'e'] [00 00 00 1E]
        long uid = ((TableManagerImpl)tbm).vm.insert(xid, raw); //调用存储接口将字节数组写入存储，返回分配的 UID
        for (Field field : fields) { //遍历表的字段，检查哪些字段是索引字段
            if(field.isIndexed()) { //对于索引字段，将字段值与记录的 UID 插入到索引树中
                field.insert(entry.get(field.fieldName), uid);
            }
        }
    }

    /**
     * 将字符串数组转换为键值对记录
     */
    private Map<String, Object> string2Entry(String[] values) throws Exception {
        if(values.length != fields.size()) {
            throw Error.InvalidValuesException;
        }
        Map<String, Object> entry = new HashMap<>();
        for (int i = 0; i < fields.size(); i++) {
            Field f = fields.get(i);
            Object v = f.string2Value(values[i]);
            entry.put(f.fieldName, v);
        }
        return entry;
    }

    /**
     * 解析 Where 条件并根据字段的索引范围获取符合条件的记录 UID 列表
     */
    private List<Long> parseWhere(Where where) throws Exception {
        long l0=0, r0=0, l1=0, r1=0;
        boolean single = false;
        Field fd = null;
        if(where == null) { //如果没有条件（where == null），则选择任意一个已索引字段，查询整个索引范围
            for (Field field : fields) {
                if(field.isIndexed()) {
                    fd = field;
                    break;
                }
            }
            // 设置查询范围为整个索引范围 [0, Long.MAX_VALUE]
            l0 = 0;
            r0 = Long.MAX_VALUE;
            //仅有一个查询范围
            single = true;
        } else {
            for (Field field : fields) {
                // 找到与 where.singleExp1.field 匹配的字段
                if(field.fieldName.equals(where.singleExp1.field)) {
                    if(!field.isIndexed()) { //如果匹配字段没有索引,抛出异常
                        throw Error.FieldNotIndexedException;
                    }
                    fd = field;
                    break;
                }
            }
            if(fd == null) { //如果未找到匹配字段，抛出异常
                throw Error.FieldNotFoundException;
            }
            CalWhereRes res = calWhere(fd, where); //计算字段 fd 上的查询范围,返回结果包含两个范围（[l0, r0] 和 [l1, r1]）以及是否为单一条件
            l0 = res.l0; r0 = res.r0;
            l1 = res.l1; r1 = res.r1;
            single = res.single;
        }
        List<Long> uids = fd.search(l0, r0);
        if(!single) {
            List<Long> tmp = fd.search(l1, r1);
            uids.addAll(tmp); //将第二次查询结果添加到 UID 列表
        }
        return uids;
    }

    class CalWhereRes {
        long l0, r0, l1, r1;
        boolean single;
    }

    /**
     * 条件的范围计算
     */
    private CalWhereRes calWhere(Field fd, Where where) throws Exception {
        CalWhereRes res = new CalWhereRes();
        switch(where.logicOp) {
            case "": // 如果逻辑运算符为空，表示只有一个条件
                res.single = true;
                FieldCalRes r = fd.calExp(where.singleExp1);
                res.l0 = r.left; res.r0 = r.right;
                break;
            case "or":
                res.single = false;
                r = fd.calExp(where.singleExp1);
                res.l0 = r.left; res.r0 = r.right;
                r = fd.calExp(where.singleExp2);
                res.l1 = r.left; res.r1 = r.right;
                break;
            case "and":
                res.single = true; //表示最终合并为一个范围
                r = fd.calExp(where.singleExp1);
                res.l0 = r.left; res.r0 = r.right;
                r = fd.calExp(where.singleExp2);
                res.l1 = r.left; res.r1 = r.right;
                if(res.l1 > res.l0) res.l0 = res.l1; //左边界：取两个范围中较大的左边界值。
                if(res.r1 < res.r0) res.r0 = res.r1; //右边界：取两个范围中较小的右边界值。
                break;
            default:
                throw Error.InvalidLogOpException;
        }
        return res;
    }

    /**
     * 将记录以字符串形式输出，格式为 [value1, value2, ..., valueN]
     */
    private String printEntry(Map<String, Object> entry) {
        StringBuilder sb = new StringBuilder("[");
        for (int i = 0; i < fields.size(); i++) {
            Field field = fields.get(i);
            sb.append(field.printValue(entry.get(field.fieldName)));
            if(i == fields.size()-1) {
                sb.append("]");
            } else {
                sb.append(", ");
            }
        }
        return sb.toString();
    }

    /**
     * 将字节数据解析为键值对的记录
     */
    private Map<String, Object> parseEntry(byte[] raw) {
        int pos = 0;
        Map<String, Object> entry = new HashMap<>();
        for (Field field : fields) {
            ParseValueRes r = field.parserValue(Arrays.copyOfRange(raw, pos, raw.length));
            entry.put(field.fieldName, r.v);
            pos += r.shift;
        }
        return entry;
    }

    /**
     * 将键值对记录序列化为字节数组
     */
    private byte[] entry2Raw(Map<String, Object> entry) {
        byte[] raw = new byte[0];
        for (Field field : fields) {
            raw = Bytes.concat(raw, field.value2Raw(entry.get(field.fieldName)));
        }
        return raw;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("{");
        sb.append(name).append(": ");
        for(Field field : fields) {
            sb.append(field.toString());
            if(field == fields.get(fields.size()-1)) {
                sb.append("}");
            } else {
                sb.append(", ");
            }
        }
        return sb.toString();
    }
}
